﻿using System;
using System.Linq;
using static LanguageExt.Prelude;
using static LanguageExt.Parsec.ParserResult;
using static LanguageExt.Parsec.Internal;
using static LanguageExt.Parsec.Prim;
using LanguageExt.Traits;

namespace LanguageExt.Parsec;

/// <summary>
/// Commonly used character parsers.
/// </summary>
public static class Char
{
    static Char()
    {
        space = satisfy(System.Char.IsWhiteSpace).label("space");
        spaces = skipMany(space).label("white space");
        control = satisfy(System.Char.IsControl).label("control");
        tab = satisfy(c => c == '\t').label("tab");
#pragma warning disable CS0618 // Type or member is obsolete
        newline = satisfy(c => c == '\n').label("lf new-line");
#pragma warning restore CS0618 // Type or member is obsolete
        LF = satisfy(c => c == '\n').label("lf new-line");
        CR = satisfy(c => c == '\r').label("cr carriage-return");
        CRLF = (from cr in ch('\r')
                from nl in ch('\n')
                select nl)
           .label("crlf new-line");
        endOfLine = either(LF, CRLF).label("new-line");
        digit = satisfy(System.Char.IsDigit).label("digit");
        letter = satisfy(System.Char.IsLetter).label("letter");
        alphaNum = satisfy(System.Char.IsLetterOrDigit).label("letter or digit");
        lower = satisfy(System.Char.IsLower).label("lowercase letter");
        upper = satisfy(System.Char.IsUpper).label("uppercase letter");
        punctuation = satisfy(System.Char.IsPunctuation).label("punctuation");
        separator = satisfy(System.Char.IsSeparator).label("separator");
        symbolchar = satisfy(System.Char.IsSymbol).label("symbolchar");
        octDigit = satisfy(c => "01234567".Contains(c)).label("octal digit");
        hexDigit = satisfy(c => System.Char.IsDigit(c) || "abcdefABCDEF".Contains(c)).label("hexadecimal digit");
        anyChar = satisfy(_ => true);
    }

    /// <summary>
    /// ch(c) parses a single character c
    /// </summary>
    /// <returns>The parsed character</returns>
    public static Parser<char> ch(char c) =>
        satisfy(x => x == c).label($"'{c}'");

    /// <summary>
    /// ch(c) parses a single character c
    /// </summary>
    /// <typeparam name="EQ">Eq〈char〉 trait</typeparam>
    /// <returns>The parsed character</returns>
    public static Parser<char> ch<EQ>(char c) where EQ : Eq<char> =>
        satisfy(x => EQ.Equals(x, c)).label($"'{c}'");

    /// <summary>
    /// The parser satisfy(pred) succeeds for any character for which the
    /// supplied function pred returns 'True'. 
    /// </summary>
    /// <returns>
    /// The character that is actually parsed.</returns>
    public static Parser<char> satisfy(Func<char, bool> pred) =>
        inp =>
        {
            if (inp.Index >= inp.EndIndex)
            {
                return EmptyError<char>(ParserError.SysUnexpect(inp.Pos, "end of stream"));
            }
            else
            {
                var ns = newstate(inp);

                if (ns.Tag == ResultTag.Consumed)
                {
                    if (pred(ns.Reply.Result))
                    {
                        return ns;
                    }
                    else
                    {
                        return EmptyError<char>(ParserError.SysUnexpect(inp.Pos, $"\"{ns.Reply.Result}\""));
                    }
                }
                else
                {
                    return EmptyError<char>(ParserError.SysUnexpect(inp.Pos, "end of stream"));
                }
            }
        };

    /// <summary>
    /// oneOf(str) succeeds if the current character is in the supplied list of 
    /// characters str. Returns the parsed character. See also satisfy
    /// </summary>
    public static Parser<char> oneOf(string str) =>
        satisfy(c => str.Contains(c));

    /// <summary>
    /// oneOf(str) succeeds if the current character is in the supplied list of 
    /// characters str. Returns the parsed character. See also satisfy
    /// </summary>
    public static Parser<char> oneOf(params char[] str) =>
        satisfy(c => str.Contains(c));

    /// <summary>
    /// As the dual of 'oneOf', noneOf(str) succeeds if the current
    /// character not in the supplied list of characters str. 
    /// 
    ///     var consonant = noneOf("aeiou")
    /// </summary>
    /// <returns>
    /// The parsed character.</returns>
    public static Parser<char> noneOf(string str) =>
        satisfy(c => !str.Contains(c));

    /// <summary>
    /// As the dual of 'oneOf', noneOf(str) succeeds if the current
    /// character not in the supplied list of characters str. 
    /// 
    ///     var consonant = noneOf("aeiou")
    /// </summary>
    /// <returns>
    /// The parsed character.</returns>
    public static Parser<char> noneOf(params char[] str) =>
        satisfy(c => !str.Contains(c));

    /// <summary>
    /// Parses a white space character (any character which satisfies 'System.Char.IsWhiteSpace')
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> space;

    /// <summary>
    /// Skips zero or more white space characters. See also 'skipMany'.
    /// </summary>
    public static readonly Parser<Unit> spaces;

    /// <summary>
    /// Parses a control character
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> control;

    /// <summary>
    /// Parses a tab
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> tab;

    /// <summary>
    /// Equivalent to `LF`. Parses a line-feed newline char (\n). 
    /// Returns the parsed character.
    /// </summary>
    [Obsolete("This parses only \\n (regardless of environment). Use (explicit) parser `LF` instead. Related parsers: `CRLF` and `endOfLine` (parsing CRLF or LF).")]
    public static readonly Parser<char> newline;

    /// <summary>
    /// Parses a carriage-return char (\r)
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> CR;

    /// <summary>
    /// Parses a line-feed newline char (\n)
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> LF;
 
    /// <summary>
    /// Parses a carriage-return then line-feed
    /// Returns the new-line.
    /// </summary>
    public static readonly Parser<char> CRLF;

    /// <summary>
    /// Parses a CRLF (see 'crlf') or LF (see 'newline') end-of-line.
    /// Returns a newline character(\'\\n\').
    /// </summary>
    public static readonly Parser<char> endOfLine;

    /// <summary>
    /// Parses a digit
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> digit;

    /// <summary>
    /// Parses a letter
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> letter;

    /// <summary>
    /// Parses a letter or digit
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> alphaNum;

    /// <summary>
    /// Parses a lowercase letter
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> lower;

    /// <summary>
    /// Parses a uppercase letter
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> upper;

    /// <summary>
    /// Parses a punctuation character
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> punctuation;

    /// <summary>
    /// Parses a separator character
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> separator;

    /// <summary>
    /// Parses a symbol character
    /// Returns the parsed character.
    /// </summary>
    public static readonly Parser<char> symbolchar;

    /// <summary>
    /// Parses an octal digit (0-7)
    /// </summary>
    public readonly static Parser<char> octDigit;

    /// <summary>
    /// Parses a hex digit (0-F | 0-f)
    /// </summary>
    public readonly static Parser<char> hexDigit;

    /// <summary>
    /// The parser anyChar accepts any kind of character.
    /// </summary>
    public readonly static Parser<char> anyChar;

    /// <summary>
    /// Parse a string
    /// </summary>
    public static Parser<string> str(string s) =>
        asString(chain(toSeq(s.AsIterable().Map(ch)))).label($"'{s}'");

    /// <summary>
    /// Parse a string case insensitive (char by char)
    /// <typeparam name="EQ">Eq〈char〉 trait</typeparam>
    /// </summary>
    public static Parser<string> str<EQ>(string s) where EQ: Eq<char>  =>
        asString(chain(toSeq(s.AsIterable().Map(ch<EQ>)))).label($"'{s}'");
}
